#pragma once

#include <iostream>
#include <string>
#include <fstream>
#include <vector>
#include <algorithm>
#include <mutex>
#include <jsoncpp/json/json.h>

#include "oj_model.hpp"
#include "oj_view.hpp"
#include "../comm/Log.hpp"
#include "../comm/util.hpp"
#include "../comm/httplib.h"

namespace ns_control
{
    using namespace ns_model;
    using namespace ns_view;
    using namespace ns_util;
    using namespace httplib;

    class Machine
    {
    public:
        std::string ip;
        int port;
        int load;
        std::mutex *mtx;

    public:
        Machine() : ip(""), port(0), load(0), mtx(new std::mutex)
        {
        }
        ~Machine()
        {}
        // 提升主机负载
        void IncLoad()
        {
            if(mtx) mtx->lock();
            ++load;
            if(mtx) mtx->unlock();
        }
        // 减小主机负载
        void DecLoad()
        {
            if(mtx) mtx->lock();
            --load;
            if(mtx) mtx->unlock();
        }

        void ResLoad()
        {
            if(mtx) mtx->lock();
            load = 0;
            if(mtx) mtx->unlock();
        }
    };

    const std::string conf_path = "./conf/server_machine.conf";

    class LoadBlance
    {
    private:
        std::vector<Machine> machine; // 提供编译服务的所有主机   这里将主机的下标(vector<Machine>)当做ip(online, offline)使用
        std::vector<int> online;      // 所有在线主机id
        std::vector<int> offline;     // 所有离线主机id
        std::mutex *mtx;
    public:
        LoadBlance():mtx(new std::mutex)
        {
            LoadConf(conf_path);            
        }
        ~LoadBlance()
        {}

        // 加载配置文件中的主机列表
        bool LoadConf(const std::string &machine_conf)
        {
            std::ifstream in(machine_conf);
            if(!in.is_open())
            {
                LOG(FATAL) << "加载主机列表失败" << "\n";
                return false;
            }

            std::string line;
            while(std::getline(in, line))
            {
                std::vector<std::string> content;
                StringUtil::StringSpilt(line, content, ":");
                if(content.size() != 2)
                {
                    LOG(WARNING) << "配置部分主机失败" << "\n";
                    continue;
                }

                Machine m;
                m.ip = content[0];
                m.port = atoi(content[1].c_str());
                
                online.push_back(machine.size());
                machine.push_back(m);
            }

            in.close();
            return true;
        }

        // 智能选择主机 轮询的方式选择最小负载的主机
        bool SmartChoice(int *id, Machine **m)
        {
            mtx->lock();
            
            int online_num = online.size();
            if(online_num == 0)
            {
                mtx->unlock();
                LOG(FATAL) << "所有的编译服务的主机已经离线，请尽快维修" << "\n";
                return false;
            }

            *id = online[0];
            *m = &machine[online[0]];
            uint64_t min_load = machine[online[0]].load;
            for(int i = 1; i < online_num; ++i)
            {
                if(min_load > machine[online[i]].load)
                {
                    *id = online[i];
                    *m = &machine[online[i]];
                    min_load = machine[online[i]].load;
                }
            }
            mtx->unlock();
            return true;
        }

        void OfflineMachine(int id)
        {
            mtx->lock();
            auto iter = online.begin();
            while(iter != online.end())
            {
                if(*iter == id)
                {   
                    machine[*iter].ResLoad();
                    offline.push_back(*iter);
                    online.erase(iter);
                    break;
                }
                ++iter;
            }
            mtx->unlock();
        }
        void OnlineMachine(){
            mtx->lock();
            online.insert(online.end(), offline.begin(), offline.end());
            offline.erase(offline.begin(), offline.end());
            mtx->unlock();

            LOG(INFO) << "所有的主机已经上线了！" << "\n";
        }

        // 打印online, offline的内容
        void ShowMachine()
        {
            std::cout << "当前在线主机列表：" << std::endl;
            for(const auto &key : online)
            {
                std::cout << key << " ";
            }
            std::cout << std::endl;
            
            std::cout << "当前离线主机列表：" << std::endl;
            for(const auto &key : offline)
            {
                std::cout << key << " ";
            }
            std::cout << std::endl;
        }
    };

    class Control
    {
    private:
        Model _model;
        View _view;
        LoadBlance _loadblance;

    public:
        Control() {}
        ~Control() {}

        bool AllQusetions(std::string *html)
        {
            std::vector<Question> questions;
            if (_model.GetAllQuestions(&questions))
            {
                sort(questions.begin(), questions.end(), [](const Question &q1, const Question &q2){
                    return atoi(q1.number.c_str()) < atoi(q2.number.c_str());
                });
                // 调用渲染题目列表方法
                _view.AllExpandHtml(questions, html);
            }
            else
            {
                *html = "获取题目列表失败";
                return false;
            }
            return true;
        }

        bool OneQuestions(const std::string &number, std::string *html)
        {
            Question q;
            if (_model.GetOneQuestion(number, &q))
            {
                // 调用渲染一道题目的方法
                _view.OneExpandHtml(q, html);
            }
            else
            {
                *html = "获取" + number + "题目失败";
                return false;
            }
            return true;
        }

        //                   题号           客户端传给oj_server的信息     oj_server传给客户端的信息
        void Judge(const std::string &number, const std::string &in_json, std::string *out_json)
        {
            // 
            //std::cout << "in_json: " << in_json << std::endl;

            Question q;
            _model.GetOneQuestion(number, &q);
            // 1.对in_json进行反序列化，得到题目的id，用户提交的代码...
            Json::Reader reader;
            Json::Value in_value;
            reader.parse(in_json, in_value);
            std::string code = in_value["code"].asString();

            // 2.拼接用户代码和测试用例
            Json::Value compiler_value;
            compiler_value["code"] = code + "\n" + q.tail;
            compiler_value["input"] = in_value["input"].asString();
            compiler_value["cpu_limit"] = q.cpu_limit;
            compiler_value["mem_limit"] = q.mem_limit;
            Json::FastWriter writer;
            std::string compiler_string = writer.write(compiler_value);

            // 3.选择负载最低的主机，发起http请求，获取结果
            // 一直选择，直到有主机可用或所有主机离线
            while(true)
            {
                int id = 0;
                Machine *m = nullptr;
                if(!_loadblance.SmartChoice(&id, &m))
                {
                    // 所有主机可能都离线了
                    break;
                }

                Client cli(m->ip, m->port);
                m->IncLoad();
                //                                                                                        缓冲区的问题吗？              上次无"\n"
                LOG(IFNO) << "选择主机成功，主机id: " << id << "详情：" << m->ip << ":" << m->port << "当前主机的负载是: " << m->load << "\n";
                if(auto res = cli.Post("/compile_and_run", compiler_string, "application/json; charset=utf-8"))
                {
                    //
                    std::cout << "res->status: " << res->status << std::endl;
                    // 4.将结果赋值给out_json
                    if(res->status == 200)
                    {
                        *out_json = res->body;
                        m->DecLoad();
                        LOG(INFO) << "请求编译和运行服务成功..." << "\n";
                        break;
                    }
                }
                else
                {
                    // 请求失败
                    LOG(ERROR) << "选择主机失败，主机id: " << id << "详情：" << m->ip << ":" << m->port << "该主机可能已经离线" << "\n";
                    _loadblance.OfflineMachine(id);
                    _loadblance.ShowMachine();
                }
                m->DecLoad();
            }
       }
    };
}